﻿using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Text;

namespace SDS.SPHealthAnalyzerRules.Delegation.Helpers
{
    public static class AD_Delegation
    {
        public enum DelegationPermission { NoDelegation, Unconstrained, Constrained_KerbOnly, Constrained_AnyProtocol }
        [System.Diagnostics.DebuggerDisplay("{ {ServiceType}, {Target}, {Port}, {ServiceName} }")]
        public struct ConstrainedDelegationTarget
        {
            public string ClassName { get; set; }
            public string Address { get; set; }
            //public string Port { get; set; }
            //public string ServiceName { get; set; }
        }
        [System.Diagnostics.DebuggerDisplay("Permission = [{Permission}], Targets = [{DelegationTargets}]")]
        public struct DelegationProperties
        {
            /// <summary>
            /// Special account option "Do not require Kerberos Preauthentication"
            /// </summary>
            public bool KerberosPreauthenticationNotRequired { get; set; }
            public bool IsSensitiveAccount { get; set; }
            public DelegationPermission Permission { get; set; }
            public IEnumerable<ConstrainedDelegationTarget> TrustedTargets { get; set; }
        }

        public static bool UserExists(string AccountName)
        {
            System.DirectoryServices.DirectorySearcher ds = new System.DirectoryServices.DirectorySearcher()
                {
                    Filter = String.Format("(sAMAccountName={0})", AccountName),
                };

            System.DirectoryServices.SearchResult result = ds.FindOne();

            return result != null;
        }
        public static DelegationProperties GetUser(string AccountName)
        {
            System.DirectoryServices.DirectorySearcher ds = new System.DirectoryServices.DirectorySearcher();
            ds.Filter = String.Format("(sAMAccountName={0})", AccountName);
            ds.PropertiesToLoad.AddRange("displayName,userAccountControl,msds-allowedToDelegateTo".Split(','));
            
            System.DirectoryServices.SearchResult result = ds.FindOne();

            ADS_USER_FLAG_ENUM userAccountControlFlags = (ADS_USER_FLAG_ENUM)result.Properties["userAccountControl"][0];
            var DelegationTargets = result.Properties["msds-allowedToDelegateTo"];

            return new DelegationProperties()
            {
                Permission = (userAccountControlFlags & ADS_USER_FLAG_ENUM.ADS_UF_TRUSTED_FOR_DELEGATION) != 0 ? DelegationPermission.Unconstrained
                           : (userAccountControlFlags & ADS_USER_FLAG_ENUM.ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) != 0 ? DelegationPermission.Constrained_AnyProtocol
                           : DelegationTargets.Count > 0 ? DelegationPermission.Constrained_KerbOnly
                           : DelegationPermission.NoDelegation,
                IsSensitiveAccount = (userAccountControlFlags & ADS_USER_FLAG_ENUM.ADS_UF_NOT_DELEGATED) != 0,
                KerberosPreauthenticationNotRequired = (userAccountControlFlags & ADS_USER_FLAG_ENUM.ADS_UF_DONT_REQUIRE_PREAUTH) != 0,
                TrustedTargets = DelegationTargets.GetEnumerable<string>().Select(t => new ConstrainedDelegationTarget() { ClassName = t.Split('/')[0], Address = t.Split('/')[1] })
            };
            
        } // DelegationProperties GetUser(...)
        
        /// <summary>
        /// Reference: http://msdn.microsoft.com/en-us/library/aa772300.aspx
        /// </summary>
        /// <remarks>
        /// DSQUERY.exe can be used to FIND accounts with these flags, using the following command:
        ///   >dsquery * -limit 0 -filter "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=1048576))" -attr name samaccountname
        ///   where 1048576 is the specific flag's value being saught
        /// </remarks>
        [Flags]
        private enum ADS_USER_FLAG_ENUM
        {
            ADS_UF_SCRIPT = 1,                                         // 0x1
            ADS_UF_ACCOUNTDISABLE = 2,                                 // 0x2
            ADS_UF_HOMEDIR_REQUIRED = 8,                               // 0x8
            ADS_UF_LOCKOUT = 16,                                       // 0x10
            ADS_UF_PASSWD_NOTREQD = 32,                                // 0x20
            ADS_UF_PASSWD_CANT_CHANGE = 64,                            // 0x40
            ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 128,              // 0x80
            ADS_UF_TEMP_DUPLICATE_ACCOUNT = 256,                       // 0x100
            ADS_UF_NORMAL_ACCOUNT = 512,                               // 0x200
            ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 2048,                   // 0x800
            ADS_UF_WORKSTATION_TRUST_ACCOUNT = 4096,                   // 0x1000
            ADS_UF_SERVER_TRUST_ACCOUNT = 8192,                        // 0x2000
            ADS_UF_DONT_EXPIRE_PASSWD = 65536,                         // 0x10000
            ADS_UF_MNS_LOGON_ACCOUNT = 131072,                         // 0x20000
            ADS_UF_SMARTCARD_REQUIRED = 262144,                        // 0x40000

            /// <summary>
            /// "Trust this user for delegation to any service (Kerberos only)"
            /// </summary>
            /// <remarks>
            /// Also known as unconstrained delegation.
            /// This account can be used to delegate a callers' credentials, to any destination.
            /// </remarks>
            ADS_UF_TRUSTED_FOR_DELEGATION = 524288,                    // 0x80000

            /// <summary>
            /// ?
            /// </summary>
            ADS_UF_NOT_DELEGATED = 1048576,                            // 0x100000
            ADS_UF_USE_DES_KEY_ONLY = 2097152,                         // 0x200000

            /// <summary>
            /// "Do not require Kerberos preauthentication"
            /// </summary>
            ADS_UF_DONT_REQUIRE_PREAUTH = 4194304,                     // 0x400000

            ADS_UF_PASSWORD_EXPIRED = 8388608,                         // 0x800000

            /// <summary>
            /// "Trust this user for delegation to specified services only (Use any authentication protocol)"
            /// </summary>
            /// <remarks>
            /// Constrained Delegation + Protocol Transition 
            /// </remarks>
            ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 16777216   // 0x1000000
        }

    } // class AD_Delegation

    public static class ExtensionMethods
    {
        public static IEnumerable<T> GetEnumerable<T>(this System.DirectoryServices.ResultPropertyValueCollection values)
        {
            foreach (var value in values)
            {
                yield return (T)value;
            }
        }
    }

} // namespace
